package net.borlis.sample.service.db.em;

import net.borlis.configuration.common.ApplicationConfiguration;
import net.borlis.configuration.context.tt.ContextFile;
import net.borlis.sample.service.db.em.lifecycle.LastLiveCopy;
import net.borlis.sample.service.db.em.lifecycle.LiveCopy;
import net.borlis.sample.service.db.em.lifecycle.WaitingForUploadCopy;
import net.borlis.sample.service.db.em.lifecycle.WorkingCopy;
import net.borlis.sample.service.db.update.Schema;
import net.borlis.sample.service.repositories.schema.ISchemaConfiguration;
import net.borlis.errors.providers.common.IServerExceptionProcessor;
import net.borlis.errors.providers.implementation.filebase.LocaleFileErrors;
import net.borlis.log.SimpleLogger;

import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import javax.enterprise.context.ApplicationScoped;
import javax.enterprise.context.RequestScoped;
import javax.enterprise.inject.Default;
import javax.enterprise.inject.Disposes;
import javax.enterprise.inject.Produces;
import javax.inject.Inject;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.Persistence;
import javax.persistence.PersistenceException;
import java.util.HashMap;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 * @author alex@borlis.net
 */
@ApplicationScoped
public class AlphaEntityManagerProvider {

    private static final String DYNAMIX_CONFIG_SERVICE = "DCS_PU";
    private static final String LOG_SEPARATOR = "----------------------------------------";

    @Inject
    private Logger logger;

    @Inject @ContextFile
    private ApplicationConfiguration config;

    @Inject
    private ISchemaConfiguration schema;

    @Inject @LocaleFileErrors
    private IServerExceptionProcessor exceptionProcessor;

    private final Map<String, String> properties = new HashMap<>();

    //@PersistenceUnit(unitName = DYNAMIX_CONFIG_SERVICE)
    private Map<DatabaseDictionary,EntityManagerFactory> emFactories;

    private EntityManagerFactory entityManagerFactory;

    @PostConstruct @SimpleLogger
    public void init() /* throws ServerException */ {
        try {

//            updateEntitySchema(CountryEntity.class);

            // get the property value and put them to jpa properties
            properties.put("hibernate.connection.driver_class",config.getDriverName());
//            properties.put("hibernate.connection.url", config.getJdbcUrlDraft() + schema.getSchemaConfiguration());
            properties.put("hibernate.connection.username", config.getJdbcUser());
            properties.put("hibernate.connection.password", config.getJdbcPassword());

            Map<DatabaseDictionary,String> urls = new HashMap<>();
            urls.put(DatabaseDictionary.DEFAULT, schema.getSchemaConfiguration()); //todo:: define the correct name!!!
            urls.put(DatabaseDictionary.CONFIGURATION_WORKING_COPY, Schema.NEW_VITO_CONFIG_WORKING); //todo:: define the correct name!!!
            urls.put(DatabaseDictionary.CONFIGURATION_WAITING_FOR_UPLOAD_COPY, Schema.NEW_VITO_CONFIG_WAITING_FOR_UPLOAD);
            urls.put(DatabaseDictionary.CONFIGURATION_LIVE_COPY, Schema.NEW_VITO_CONFIG_LIVE_COPY);
            urls.put(DatabaseDictionary.CONFIGURATION_LAST_LIVE_COPY, Schema.NEW_VITO_CONFIG_LAST_LIVE);

            // -=-=-=-=-=-=-
            logger.log(Level.CONFIG, LOG_SEPARATOR);
            logger.log(Level.CONFIG, "** Current system properties **");
            for (Object property : System.getProperties().keySet()) {
                logger.log(Level.CONFIG, property + "=" + System.getProperties().get(property));
            }
            logger.log(Level.CONFIG, LOG_SEPARATOR);

            if (emFactories == null) {
                emFactories = new HashMap<>();
                for (DatabaseDictionary db : DatabaseDictionary.values()) {
                    Map<String, String> lcProperties = new HashMap<>();
                    lcProperties.putAll(properties);
                    lcProperties.put("hibernate.connection.url", config.getJdbcUrlDraft() + urls.get(db));
                    lcProperties.put("hibernate.ejb.entitymanager_factory_name", db.name());
                    emFactories.put(
                            db,
                            Persistence.createEntityManagerFactory(DYNAMIX_CONFIG_SERVICE, lcProperties));
                    logger.info("entity managers " + db + " factory created");
                    logger.log(Level.CONFIG, LOG_SEPARATOR);
                    logger.log(Level.CONFIG, "* Entity Manager Factory init **");
                    logger.log(Level.CONFIG, "* DB connection = " + emFactories.get(db).getProperties().get("hibernate.connection.url"));
                    logger.log(Level.CONFIG, "* Cache = " + emFactories.get(db).getCache().toString());
                    logger.log(Level.CONFIG, LOG_SEPARATOR);
                }
            }
        } catch (PersistenceException error) {
            logger.log(Level.SEVERE, "Failed to init EntityManagerFactory.", error);
            throw exceptionProcessor.newServerException("500", error);
        }
    }

    private EntityManager produceEntityManager(DatabaseDictionary state) /* throws ServerException */ {
        try {
            EntityManager em = emFactories.get(state).createEntityManager();
            logger.log(Level.INFO, "-> Entity Manager hashCode = " + em.hashCode());
            return em;
        } catch (Exception exception) {
            logger.log(Level.SEVERE, "Failed to get current session object.", exception);
            throw exceptionProcessor.newServerException("500", exception);
        }
    }

    @Produces @RequestScoped @Simple @Default
    public EntityManager produceSimpleEntityManager() /* throws ServerException */ {
       return produceEntityManager(DatabaseDictionary.DEFAULT);
    }

    @Produces @RequestScoped @WorkingCopy
    public EntityManager produceWorkingCopyEntityManager() /* throws ServerException */ {
        return produceEntityManager(DatabaseDictionary.CONFIGURATION_WORKING_COPY);
    }

    @Produces @RequestScoped @WaitingForUploadCopy
    public EntityManager produceWaitingForUploadEntityManager() /* throws ServerException */ {
        return produceEntityManager(DatabaseDictionary.CONFIGURATION_WAITING_FOR_UPLOAD_COPY);
    }

    @Produces @RequestScoped @LiveCopy
    public EntityManager produceLiveEntityManager() /* throws ServerException */ {
        return produceEntityManager(DatabaseDictionary.CONFIGURATION_LIVE_COPY);
    }

    @Produces @RequestScoped @LastLiveCopy
    public EntityManager produceLastLiveEntityManager() /* throws ServerException */ {
        return produceEntityManager(DatabaseDictionary.CONFIGURATION_LAST_LIVE_COPY);
    }

    public void disposeSimpleEntityManager(@Disposes @Simple EntityManager em){
        disposeCurrentEntityManager(DatabaseDictionary.DEFAULT, em);
    }

    public void disposeWorkingCopyEntityManager(@Disposes @WorkingCopy EntityManager em){
        disposeCurrentEntityManager(DatabaseDictionary.CONFIGURATION_WORKING_COPY, em);
    }

    public void disposeWaitingForUploadCopyEntityManager(@Disposes @WaitingForUploadCopy EntityManager em){
        disposeCurrentEntityManager(DatabaseDictionary.CONFIGURATION_WAITING_FOR_UPLOAD_COPY, em);
    }

    public void disposeLiveCopyEntityManager(@Disposes @LiveCopy EntityManager em){
        disposeCurrentEntityManager(DatabaseDictionary.CONFIGURATION_LIVE_COPY, em);
    }

    public void disposeLastLiveEntityManager(@Disposes @LastLiveCopy EntityManager em){
        disposeCurrentEntityManager(DatabaseDictionary.CONFIGURATION_LAST_LIVE_COPY, em);
    }

    private void disposeCurrentEntityManager(DatabaseDictionary dd, EntityManager em){
        try {
            logger.info("entity manager " + em.hashCode() + " for " + dd + " disposed");
            if (em.isOpen()) {
                logger.info("entity manager closed");
                em.close();
            }
        } catch (Exception e) {
            logger.log(Level.SEVERE, "Failed to dispose EntityManager " + em.hashCode(), e);
            throw e;
        }
    }

    @PreDestroy
    public void destroy() {
        try {
            for (DatabaseDictionary db : DatabaseDictionary.values()) {
                if (emFactories.get(db).isOpen()) {
                    logger.info("entity managers factory " + db + " closed");
                    emFactories.get(db).close();
                }
            }
        } catch (Exception e) {
            logger.log(Level.SEVERE, "Failed to dispose EntityManagerFactories ", e);
            throw e;
        }
    }
}